package cn.z2huo.knowledge.concurrency.lock.stampedlock;

import cn.hutool.core.thread.ThreadUtil;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;

/**
 * <p>
 *
 * @author z2huo
 */
class StampedLockTest {

    /**
     * 共享数据
     */
    private final static Map<String, String> MAP = new HashMap<>();

    private final static StampedLock LOCK = new StampedLock();

    /**
     * 对共享数据进行写操作
     */
    public static Object put(String key, Object value) {
        long stamp = LOCK.writeLock();
        try {
            System.out.println(LocalDateTime.now() + " get write lock");
            // 业务操作。。。
            ThreadUtil.sleep(1000);
            String put = MAP.put(key, value.toString());
            return put;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println(LocalDateTime.now() + " release write lock");
            LOCK.unlockWrite(stamp);
        }
        return null;
    }

    /**
     * 对共享数据的悲观读操作
     */
    public static Object pessimisticRead(String key) {
        System.out.println(LocalDateTime.now() + " 锁进入过写模式，只能悲观读");
        // 进入了写锁模式，只能获取悲观读锁
        long stamp = LOCK.readLock();
        try {
            // 成功获取到读锁
            System.out.println(LocalDateTime.now() + " get read lock");
            // 业务操作。。。
            ThreadUtil.sleep(1000);
            String value = MAP.get(key);
            return value;
        } finally {
            System.out.println(LocalDateTime.now() + " release read lock");
            LOCK.unlockRead(stamp);
        }
    }

    /**
     * 对共享数据的乐观读操作
     */
    public static Object optimisticRead(String key) {
        String value = null;
        // 尝试进行乐观读
        long stamp = LOCK.tryOptimisticRead();
        if (stamp != 0) {
            System.out.println(LocalDateTime.now() + " get optimistic read lock");
            ThreadUtil.sleep(1000);
            value = MAP.get(key);
        } else {
            // 0 == stamp 表示当前为写锁模式
            System.out.println(LocalDateTime.now() + " get optimistic read lock fail");
            // LOCK 已经进入写模式，使用悲观读方法
            return pessimisticRead(key);
        }

        // 乐观读操作已经间隔了一段时间，期间可能发生写入
        // 所以，需要验证乐观读的印戳值是否有效，即判断 LOCK 是否进入过写模式
        if (!LOCK.validate(stamp)) {
            // 乐观读的印戳值无效，表明写锁被占用过
            System.out.println(LocalDateTime.now() + " optimistic read lock stamp expired");
            // 写锁已经被抢占，进入了写锁模式，只能通过悲观读锁再一次读取最新值
            return pessimisticRead(key);
        } else {
            // 乐观读的印戳值有效，表明写锁没有被占用过
            // 不用加悲观读锁而直接读，减少了读锁的开销
            System.out.println(LocalDateTime.now() + " optimistic read lock stamp don't expired");
            return value;
        }
    }

    public static void main(String[] args) {
        Runnable writeRunnable = () -> put("key", "value");
        Runnable readRunnable = () -> optimisticRead("key");

        new Thread(writeRunnable).start();

        new Thread(readRunnable).start();
    }

}
